/*
 * $Id: MainApp.cc 348 2009-03-05 04:06:08Z chunghh $
 */

#include "Globals.h"
#include "MainApp.h"

#include <xp/bas/sysapi.h>
#include <xp/bas/stdlib.h>
#include <xp/bas/MutexLock.hpp>
#include <errno.h>

MainApp::MainApp (): tcpgate (XP_T("tcpgate"))
{
	parent_pid = 0;
	nanny_pid = 0;
	child_pid = 0;

	setup_signal_handlers ();
	setSysconfDir (XP_NULL);
}

MainApp::~MainApp () 
{
	restore_signal_handlers ();
}

int MainApp::run ()
{
	stop_requested = false;

	parent_pid = xp_getpid();

	while (!stop_requested) 
	{
		int status;

		nanny_pid = xp_fork ();
		if (nanny_pid == -1) return -1;
		if (nanny_pid == 0) return run_nanny ();

		for (;;) 
		{
			xp_pid_t wpid = xp_waitpid (nanny_pid, &status, 0);
			if (wpid == -1)
			{
				if (errno == EINTR) continue;
				return -1;
			}
			if (wpid == nanny_pid) break;
		}
		if (XP_WIFEXITED(status) && XP_WEXITCODE(status) == 0) break;

		xp_sleep (1000);
	}

	return 0;
}

int MainApp::run_nanny ()
{
	// nanny_pid would be still 0 without this in the nanny itself
	nanny_pid = xp_getpid ();  
	xp_setpgid (nanny_pid, 0);

	child_pid = xp_fork ();
	if (child_pid == -1) return -1;
	if (child_pid == 0) 
	{
		try 
		{
			//return run_main_task ();
			xp_exit (run_main_task ());
		}
		catch (...) { return -1; }
	}

	int status;
	for (;;) 
	{
		xp_pid_t wpid = xp_waitpid (child_pid, &status, 0);
		if (wpid == -1)
		{
			if (errno == EINTR) continue;
			return -1;
		}
		if (wpid == child_pid) break;
	}
	if (XP_WIFEXITED(status) && XP_WEXITCODE(status) == 0) return 0;

	xp_kill (0, SIGKILL); // xp_kill all processes in my process group
	return -1;
}

int MainApp::run_main_task ()
{
	child_pid = xp_getpid ();  // just in case.

	load_env_file ();
	if (app_mode & APP_MODE_FOREGROUND) 
	{
		xp_log_settarget (&app_log,
			xp_log_gettarget(&app_log) | XP_LOG_CONSOLE);
	}

#if 0
	{
		MutexLock lock (&app_main_mutex);
		try { app_main = new MainApp (); }
		catch (...)
		{
			XP_LOG0 (&app_log, APP_NAME, XP_LOG_CRITICAL,
				XP_T("failed to instantiate the main application"));
			return -1;
		}
	}

	int ret_code;
	if (app_main->run() == -1) 
	{
		XP_LOG0 (&app_log, APP_NAME, XP_LOG_CRITICAL,
			XP_T("failed to launch the main application"));
		ret_code = -1;
	}	
	else ret_code = app_main->returnCode();

	{
		MutexLock lock (&app_main_mutex);
		app_main = XP_NULL;
	}

	return ret_code;
#endif

	if (Socket::startup () == -1)
	{
		XP_LOG0 (&app_log, XP_NULL, XP_LOG_CRITICAL,
			XP_T("cannot start up socket subsystem"));
		return -1;
	}

	stop_requested = false;
	ret_code = 0;

	XP_LOG2 (&app_log, XP_NULL, XP_LOG_KERN,
		XP_T("started %s %s"),
		APP_NAME, APP_VERSION
	);

	if (process_system_config() <= -1) goto exit_main_app_1;
	if (stop_requested) goto exit_main_app_1;

	run_tcp_gate_thread ();

exit_main_app_1:
	XP_LOG0 (&app_log, XP_NULL, XP_LOG_KERN, XP_T("exiting"));

exit_main_app_3:
	Socket::cleanup ();
	return ret_code;
}

int MainApp::stopWithCode (int code)
{
	this->ret_code = code;
	this->stop_requested = true;
	this->tcpgate.thread.stop ();
	return 0;
}

int MainApp::stop ()
{
	return stopWithCode (0);
}

int MainApp::shutdown ()
{
	return stopWithCode (0);
}

void MainApp::handleSignal (int sig)
{
	xp_pid_t pid = xp_getpid();

	if (pid == parent_pid) 
	{
		xp_kill (nanny_pid, sig);
		//return 0;
	}
	else if (pid == nanny_pid) 
	{
		xp_kill (child_pid, sig);
		//return 0;
	}
	else 
	{
		/*return*/ this->stop ();
	}
}

void MainApp::setup_signal_handlers ()
{
	// ignoreSignal(32) caused the system to freeze on Linux
	//ignoreAllSignals (); 
	
	//ignoreSignal (SIGCHLD);
#ifdef SIGPIPE
	ignoreSignal (SIGPIPE);
#endif

#ifdef SIGHUP
	manageSignal (SIGHUP);
#endif
#ifdef SIGINT
	manageSignal (SIGINT);
#endif
#ifdef SIGQUIT
	manageSignal (SIGQUIT);
#endif
#ifdef SIGTERM
	manageSignal (SIGTERM);
#endif
}

void MainApp::restore_signal_handlers ()
{
	//defaultifyAllSignals ();

#ifdef SIGCHLD
	defaultifySignal (SIGCHLD);
#endif
#ifdef SIGPIPE
	defaultifySignal (SIGPIPE);
#endif
#ifdef SIGHUP
	defaultifySignal (SIGHUP);
#endif
#ifdef SIGINT
	defaultifySignal (SIGINT);
#endif
#ifdef SIGQUIT
	defaultifySignal (SIGQUIT);
#endif
#ifdef SIGTERM
	defaultifySignal (SIGTERM);
#endif
}

void MainApp::setSysconfDir (const xp_char_t* dir)
{
        xp_char_t pbuf[XP_PATH_MAX + 1];

        xp_strxapp (
                pbuf, XP_COUNTOF(pbuf),
                (dir? dir: XP_T(APP_CFG_DIR)),
                XP_T("/"), APP_INI_FILE, XP_NULL
        );

        env.file.setName (pbuf);

	/* any other data files ??? */
}

void MainApp::load_env_file ()
{
	env.file.chmod (APP_FILE_PERM);

	env.data.preLoad ();
	if (env.file.read (&env.data) <= -1) 
	{
		XP_LOG1 (&app_log, APP_NAME, XP_LOG_INFO,
			XP_T("cannot load %s"), 
			env.file.name());
	}
	env.data.postLoad ();
}

int MainApp::process_system_config ()
{
	// set up file permissions
	if (app_log.type & XP_LOG_FILE)
		xp_chmod (app_log.target.file, APP_FILE_PERM);

	return 0;
}

int MainApp::run_tcp_gate_thread ()
{
	bool gate_enable_tcp;
	bool gate_tcp_local_only;
	xp_char_t gate_tcp_service[TcpAddress::MAX_SERVICE_LEN + 1];

	bool old_gate_enable_tcp = false;
	bool old_gate_tcp_local_only = false;
	xp_char_t old_gate_tcp_service[XP_COUNTOF(gate_tcp_service)] = { XP_T('\0') };

	int counter = 0;

	while (!stop_requested) 
	{
		{
			MutexLock lock (&env.mutex);
			gate_enable_tcp = env.data.gateEnableTcp ();
			gate_tcp_local_only = env.data.gateTcpLocalOnly ();
			xp_strxcpy (gate_tcp_service, 
				XP_COUNTOF(gate_tcp_service), env.data.gateTcpService());

			if (tcpgate.forced_service[0] != XP_T('\0'))
			{
				/* the service has been specified on the
				 * command line */
				gate_enable_tcp = true;
				xp_strxcpy (gate_tcp_service,
					xp_countof(gate_tcp_service),
					tcpgate.forced_service
				);
			}
		}

		if (gate_enable_tcp) 
		{ 
			if (gate_enable_tcp != old_gate_enable_tcp ||
			    gate_tcp_local_only != old_gate_tcp_local_only ||
			    xp_strcmp(gate_tcp_service, old_gate_tcp_service) != 0) 
				{
				// disabled to enabled and/or tcp_local_only changed. 
				if (old_gate_enable_tcp && tcpgate.thread.stop() == -1) 
				{
					XP_LOG1 (&app_log, XP_NULL, XP_LOG_CRITICAL,
						XP_T("cannot stop %s"), tcpgate.thread.name());
					stopWithCode (1);
					break;
				}
				if (old_gate_enable_tcp && tcpgate.thread.join() == -1) 
				{
					XP_LOG1 (&app_log, XP_NULL, XP_LOG_CRITICAL,
						XP_T("cannot join %s"), tcpgate.thread.name());
					stopWithCode (1);
					break;
				}

				TcpAddress addr;
				if (TcpAddress::resolve (gate_tcp_service,
					(gate_tcp_local_only? XP_T(""): XP_NULL), &addr) == -1) 
				{
					XP_LOG1 (&app_log, XP_NULL, XP_LOG_CRITICAL,
						XP_T("cannot resolve the service - %s"),
						gate_tcp_service);
				}
				else 
				{
					tcpgate.thread.setBindingAddress (addr);
		
					MutexLock startup_lock (&tcpgate.startup_mutex);
					if (tcpgate.thread.start() == -1) 
					{
						XP_LOG1 (&app_log, XP_NULL, XP_LOG_CRITICAL,
							XP_T("cannot start the service - %s"), 
							tcpgate.thread.name());
					}
				}
			}
		}
		else if (gate_enable_tcp != old_gate_enable_tcp) 
		{
			// enabled -> disabled
			if (tcpgate.thread.stop() == -1) 
			{
				XP_LOG1 (&app_log, XP_NULL, XP_LOG_CRITICAL,
					XP_T("cannot stop %s"), tcpgate.thread.name());
				stopWithCode (1);
				break;
			}
			if (tcpgate.thread.join() == -1) 
			{
				XP_LOG1 (&app_log, XP_NULL, XP_LOG_CRITICAL,
					XP_T("cannot join %s"), tcpgate.thread.name());
				stopWithCode (1);
				break;
			}
		}

		old_gate_enable_tcp = gate_enable_tcp;
		old_gate_tcp_local_only = gate_tcp_local_only;
		xp_strxcpy (old_gate_tcp_service,
			XP_COUNTOF(old_gate_tcp_service), gate_tcp_service);

		// TODO: condition_timed_wait would be a better way.
		xp_sleep (1000);
		counter++;
	}

	tcpgate.thread.join ();
	return 0;
}
